iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 30
1
AI & Data

爬蟲在手、資料我有 - 30 天 Scrapy 爬蟲實戰系列 第 30

【Day 29】在 Scrapy 中處理請求和回應 - Downloader Middlewares

  • 分享至 

  • xImage
  •  

Day 22 的文章中有提到 Scrapy EngineDownloader 間的資料傳遞會經過一系列的 Downloader Middlewares,也就是下圖紅框中的區塊。

https://ithelp.ithome.com.tw/upload/images/20191014/20107875vBYY8ziqZk.png

記得 Day 20Day 21 介紹的反爬蟲機制嗎?只要是可以在發送請求前動手腳的應對方式(例如 User-Agent、puppeteer),都很適合在 Downloader Middlewares 中實作,今天就來介紹怎麼使用 Downloader Middlewares

建立 Middleware 元件

每一個 Middleware 元件都是一個 Python 類別,不需要繼承其他類別,只要視需求實作部分的下列方法即可:

專案建立時,目錄中有一個 middlewares.py 檔案,其中有 Scrapy 根據專案名稱自動建立的 IthomeCrawlersDownloaderMiddleware 類別,就可以看出每個元件可以實作的方法和每個方法應該回傳的類型。

class IthomeCrawlersDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        return None

    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

更換 User-Agent

之前有介紹利用 fake-useragent 套件來隨機產生 UA,這種每個請求都要處理的邏輯就很適合放在 Downloader Middlewares 中處理。

記得要先安裝 fake-useragent 套件:pipenv install fake-useragent

middlewares.py 檔案中加入以下程式碼

from fake_useragent import UserAgent

class RandomUserAgentMiddleware(object):
    def __init__(self, ua_type):
        self.ua = UserAgent()
        self.ua_type = ua_type

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            ua_type=crawler.settings.get('RANDOM_UA_TYPE', 'random')
        )

    def process_request(self, request, spider):
        def get_ua():
            # 根據設定中 RANDOM_UA_TYPE 的值來隨機產生 UA
            return getattr(self.ua, self.ua_type)

        request.headers.setdefault('User-Agent', get_ua())
        
    def process_response(self, request, response, spider):
        '''測試用,確認有隨機產生 UA
        實際使用時可以拿掉
        '''
        spider.logger.info(f'User-Agent of [{request.url}] is [{request.headers["User-Agent"]}]')
        
        return response

跟 Item Pipelines 一樣,Downloader Middlewares 元件也需要加入到執行序列中。在 settings.py 檔案中有一個 dict 型態的 DOWNLOADER_MIDDLEWARES 變數,key 是元件的完整名稱,value 是 0~1000 的整數,請求會由數字小到大依序執行,回應則會由數字大到小依序執行。

Scrapy 已經有預設啟用一些 Download Middlewares 元件了,加入自訂的元件時需要額外注意執行順序。其中預設有啟用 UserAgentMiddleware,如果要改用剛剛建立的元件,建議把這個停用。

DOWNLOADER_MIDDLEWARES = {
   'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
   'ithome_crawlers.middlewares.RandomUserAgentMiddleware': 500,
}

啟動爬蟲時可以看到元件已被加入 Middlewares 序列中。

https://ithelp.ithome.com.tw/upload/images/20191014/20107875jGmcBUVjwV.png

同時在 log 中也可以看到確實有隨機產生 UA。

https://ithelp.ithome.com.tw/upload/images/20191014/20107875kevMHKjG0U.png

參考資料


默默的過了 30 天,好像前面爬蟲基礎寫了太多 ,絕對不是拖稿,Scrapy 還有很多東西沒講......

所以!

接下來還是會繼續更新,把預期要寫的東西寫完! 但更新頻率可以會變成兩天一篇之類的


上一篇
【Day 28】Item Pipelines 應用 - 儲存資料到 MongoDB
下一篇
【Day 30】在程式中啟動 Scrapy 爬蟲
系列文
爬蟲在手、資料我有 - 30 天 Scrapy 爬蟲實戰33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言